﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using CloudShot.Core.Interfaces;
using CloudShot.Core.Types;
using CloudShot.Core.Utils;
using DropNet;
using DropNet.Exceptions;
using DropNet.Models;

namespace DropboxImageStorage
{
	public class DropboxStorage : IImageStorage
	{
		private const string FOLDER_TO_SAVE = @"/";

		private readonly DropboxStorageSettingsControl _settingsUIHolder;
		private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();

		private ISettingsRepository _settingsRepository;

		#region Properties

		public Image Logo { get { return LocalResources.dropbox; } }

		public string Name { get { return "Dropbox"; } }

		public string Description { get { return "Image will be saved in Dropbox and url will be copied to clipboard"; } }

		public IEnumerable<ImageSaveFormat> SupportedFormats
		{
			get
			{
				return new[]
				{
					ImageSaveFormat.Png, 
					ImageSaveFormat.Jpg, 
					ImageSaveFormat.Jpeg, 
					ImageSaveFormat.Gif
				};
			}
		}

		#endregion

		public DropboxStorage()
		{
			_settingsUIHolder = new DropboxStorageSettingsControl(this);
		}

		#region Settings

		public ImageSaveFormat CurrentImageFormat
		{
			get { return GetSetting<ImageSaveFormat>(); }
			internal set { SetSetting(value); }
		}

		internal bool ShortenUrls
		{
			get { return GetSetting<bool>(); }
			set { SetSetting(value); }
		}

		internal bool IsAuthorized { get { return Token != null; } }

		private TokenStorage Token
		{
			get { return Base64Utils.DeserializeFromBase64(GetSetting<string>()) as TokenStorage; }
			set
			{
				string token = Base64Utils.SerializeToBase64(value);
				SetSetting(token);
			}
		}

		private T GetSetting<T>([CallerMemberName] string name = "")
		{
			return _settingsRepository.GetSetting<T>(name);
		}

		private void SetSetting<T>(T value, [CallerMemberName] string name = "")
		{
			_settingsRepository.SetSetting(name, value);
		}

		#endregion

		public async Task<ShotInfo> SaveImage(Image image, string name, ImageFormat saveImageFormat)
		{
			if (!IsAuthorized)
			{
				if (MessageBox.Show(LocalResources.AuthorizationQuestion, Application.ProductName,
					MessageBoxButtons.YesNo, MessageBoxIcon.Information) != DialogResult.Yes)
					return null;

				bool signedIn = await SignIn();

				if (!signedIn)
					return null;
			}

			var result = await SaveImageInternal(image, name, saveImageFormat, ShortenUrls);
			return result;
		}

		private async Task<ShotInfo> SaveImageInternal(Image image, string name, ImageFormat saveImageFormat, bool shortUrl)
		{
			//TODO: надо подумать как быть с saveImageFormat, т.к. в настройки записывается CurrentImageFormat, но его тут нельзя использовать, т.к. формат для видео другой
			const int repetitions = 5;
			const int waitTime = 200;

			if (Token == null)
				throw new UnauthorizedAccessException("Authorization info was corrupted. Please reauthorize in Settings -> SignIn.");

			var client = new DropNetClient(DropboxTokens.ApplicationKey, DropboxTokens.ApplicationSecret, Token.Key, Token.Secret);
			client.UseSandbox = true;

			MetaData metaData;

			// build the data stream
			using (var data = new MemoryStream())
			{
				image.Save(data, saveImageFormat);
				data.Position = 0;// reset stream
				metaData = await client.UploadFileTask(FOLDER_TO_SAVE, name, data.ToArray());// upload data
			}

            if (metaData == null || string.IsNullOrEmpty(metaData.Path))
                return null;

			ShareResponse share = null;

			for (int i = 0; i < repetitions; i++)
			{
				share = await client.GetShareTask(metaData.Path, shortUrl);

				if (share != null)
					break;

				await Task.Delay(waitTime);
			}

			if (share == null)
				return null;

			return new ShotInfo(name, share.Url, image);
		}

		public async Task<bool> SignIn()
		{
			if (string.IsNullOrEmpty(DropboxTokens.ApplicationKey))
				throw new NullReferenceException("ApplicationKey not set.");

			if (string.IsNullOrEmpty(DropboxTokens.ApplicationSecret))
				throw new NullReferenceException("ApplicationSecret not set.");

			var client = new DropNetClient(DropboxTokens.ApplicationKey, DropboxTokens.ApplicationSecret);
			string authUrl = client.GetTokenAndBuildUrl();

			//open auth url in default web browser
			Process.Start(authUrl);

			CancellationToken cancellationToken = _cancellationTokenSource.Token;
			UserLogin userLogin = await Task.Run(async () => await GetAccessToken(client, cancellationToken), cancellationToken);
			if (userLogin == null)
				return false;

			Token = new TokenStorage(userLogin.Token, userLogin.Secret);
			return true;
		}

		private static async Task<UserLogin> GetAccessToken(DropNetClient client, CancellationToken cancellationToken)
		{
			const int waitTime = 2000;
			const int repetitions = 150;//it is just 5 minutes

			UserLogin accessToken = null;
			//https://www.dropbox.com/developers/start/authentication#python
			//We don't want anyone other than the user who requested access using this URL, 
			//which is why the request tokens are only live for five minutes. 
			//If the user sits on this link for greater than five minutes, 
			//we'll politely request they return to your app to try again.
			for (int i = 0; i < repetitions; ++i)
			{
				if (cancellationToken.IsCancellationRequested)
					return null;

				await Task.Delay(waitTime, cancellationToken);

				try
				{
					accessToken = client.GetAccessToken();
					if (accessToken != null)
						break;
				}
				catch (DropboxException)
				{
				}
			}

			return accessToken;
		}

		public void SignOut()
		{
			_cancellationTokenSource.Cancel();
			Token = null;
		}

		public ISettingsRepository SettingsRepository { get { return _settingsRepository; } }

		public void Initialize(ISettingsRepository settingsRepository)
		{
			_settingsRepository = settingsRepository;
		}

		public ISettingsUIHolder SettingsUIHolder
		{
			get { return _settingsUIHolder; }
		}
	}
}